---
id: process-service
title: 25. 辅助角色服务
sidebar_label: 25. 辅助角色服务 (Worker Service)
---

import useBaseUrl from "@docusaurus/useBaseUrl";

## 25.1 关于辅助角色服务

`.NET Core 3.0` 新增了 `Worker Service` 的新项目模板，**可以编写长时间运行的后台服务，并且能轻松的部署成 `Windows服务` 或 `Linux 守护程序`**。

## 25.2 如何创建 `Worker Service`

通过 `Visual Studio 2019` 提供的 `Worker Service` 可直接创建。如图：

<img src={useBaseUrl("img/wk.png")} />

## 25.3 创建 `Worker`

当我们创建好 `Worker Service` 项目时，已经自带了一个 `Worker` 类并继承自 `BackgroundService` 基类。

`Worker` 正是我们辅助角色的主要工作类，在这里我们编写我们所有的业务逻辑。通常 `Worker` 默认格式为：

```cs {9,18}
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace FurionWorkers
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}
```

**当我们创建了 `Worker` 类之后，需要在 `Program.cs` 中进行注册**，如：

```cs {17}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FurionWorkers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}
```

:::important 小知识

如果使用了 `Furion` 包后可实现自动注册。

:::

### 25.3.1 多个 `Worker`

`Worker Service` 是支持定义多个 `Worker` 进行协调工作的，每个 `Worker` 是完全独立的工作环境，但可共享同一主进程信息。

### 25.3.2 生命周期

`Worker Service` 为 `Worker` 提供了三个执行方法，分别代表三个生命周期：

- `StartAsync`：负责启动 `Worker Service`，如果调用 `StartAsync` 方法的线程被一直阻塞了，那么 `Worker Service` 的启动就一直完成不了
- `ExecuteAsync`：`Worker Service` 真正实现业务逻辑的地方，这里不能调用阻塞代码！！！
- `StopAsync`：负责结束 `Worker Service`，如果调用 `StopAsync` 方法的线程被一直阻塞了，那么 `Worker Service` 的结束就一直完成不了

```cs {19,25,35}
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace FurionWorkers
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        // 启动
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return base.StartAsync(cancellationToken);
        }

        // 执行逻辑
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }

        // 停止
        public override Task StopAsync(CancellationToken cancellationToken)
        {
            return base.StopAsync(cancellationToken);
        }
    }
}
```

## 25.4 集成 `Furion`

`Worker Service` 集成 `Furion` 非常方便，只需要安装 `Furion` 的包即可，并在 `Program.cs` 中调用 `.Inject()` 方法，如：

```cs {15}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FurionWorkers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .Inject()
                .ConfigureServices((hostContext, services) =>
                {
                    // 以下代码可不用编写，Furion 已实现自动注册 Worker;
                    // services.AddHostedService<Worker>();
                });
    }
}
```

默认情况下，`Inject()` 方法注册了 `日志、缓存、依赖注入、加载配置、自定义 Startup` 功能。

:::tip 小知识

集成 `Furion` 后会自动扫描 `Worker` 类并实现自动注册。

:::

## 25.5 注册服务

`Worker Service` 注册服务和 `Web` 略有不同，`Web` 主要在 `Starup.cs` 类中注册，`Worker Service` 在 `Program.cs` 启动类的 `ConfigureServices` 方法中注册，如：

```cs {16-28}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FurionWorkers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .Inject()
                .ConfigureServices((hostContext, services) =>
                {
                    // 注册数据库服务
                    services.AddDatabaseAccessor(options =>
                    {
                        options.AddDb<DefaultDbContext>();
                    });

                    // 注册远程请求
                    services.AddRemoteRequest();

                    // 等等其他服务注册
                });
    }
}
```

## 25.6 实现定时任务

`Furion` 框架同时也为 `Worker Service` 提供了定时任务的支持。

### 26.6.1 间隔执行方式

```cs {23-27}
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 间隔执行任务
                await SpareTime.DoAsync(1000, () =>
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken);
            }
        }
    }
}
```

### 26.6.2 `Cron` 表达式格式化方式，

```cs {23-27}
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 执行 Cron 表达式任务
                await SpareTime.DoAsync("*/5 * * * * *", () =>
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken, CronFormat.IncludeSeconds);
            }
        }
    }
}
```

## 25.7 依赖注入使用

`Worker Service` 只为 `Worker` 提供了**单例作用域**的服务注入，如果需要注入瞬时或作用域对象，需手动创建作用域，如：

```cs {7-8,19-31}
public class Worker : BackgroundService
{
    // 日志对象
    private readonly ILogger<JobService> _logger;

    // 服务工厂
    private readonly IServiceScopeFactory _scopeFactory;
    public Worker(ILogger<Worker> logger
        , IServiceScopeFactory scopeFactory)
    {
        _logger = logger;
        _scopeFactory = scopeFactory;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                var services = scope.ServiceProvider;

                // 获取数据库上下文
                var dbContext = Db.GetDbContext(services);

                // 获取仓储
                var respository = Db.GetRepository<Person>(services);

                // 解析其他服务
                var otherService = services.GetService<XXX>();
            }
        }

        return Task.CompletedTask;
    }
}
```

## 25.8 部署到操作系统

`Worker Service` 支持部署到 `Windows Service` 中 或 `Linux 守护进程中`

### 25.8.1 部署到 `Windows Service`

- **第一步**

安装 `Microsoft.Extensions.Hosting.WindowsServices` 拓展包

- **第二步**

在 `Program.cs` 中添加 `.UseWindowsService()`

```cs {15}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FurionWorkers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseWindowsService()
                .Inject()
                .ConfigureServices((hostContext, services) =>
                {
                    // 以下代码可不用编写，Furion 已实现自动注册 Worker;
                    // services.AddHostedService<Worker>();
                });
    }
}
```

- **第三步**

发布 `Worker Service`，可通过 `dotnet publish -c Release -o C:\FurionWorker` 命令发布或通过 `Visual Studio 2019` 发布。

- **第四步**

通过 `sc.exe` 工具来管理并创建 `Windows` 服务，通过 **管理员模式** 并打开控制台，输入：

```cmd
sc.exe create FurionWorkerServices binPath= C:\FurionWorker\FurionWorker.exe
```

注意`=`后面要有一个空格  
创建成功后可通过 `sc.exe query FurionWorkerServices` 查看服务状态。

- **第五步**

启动服务：`sc.exe start FurionWorkerServices`，启动之后就可以在 `Windows` 服务工具中查看了。

停止服务：`sc.exe stop NETCoreDemoWorkerService`

删除服务：`sc.exe delete NETCoreDemoWorkerService`

:::important 特别提醒

以上所有 `sc.exe` 命令必须在 **管理员** 模式下进行。

:::

### 25.8.2 部署到 `Linux 守护程序`

- **第一步**

安装 `Microsoft.Extensions.Hosting.Systemd` 拓展包

- **第二步**

在 `Program.cs` 中添加 `.UseSystemd()`

```cs {15}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FurionWorkers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSystemd()
                .Inject()
                .ConfigureServices((hostContext, services) =>
                {
                    // 以下代码可不用编写，Furion 已实现自动注册 Worker;
                    // services.AddHostedService<Worker>();
                });
    }
}
```

部署到 `Linux 守护进程` 就是这么简单。

## 25.9 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
